package com.example.imagecompressor.service;

import com.example.imagecompressor.model.OutputFormat;
import net.coobird.thumbnailator.Thumbnails;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;

@Service
public class ImageProcessingService {

    private static final Logger logger = LoggerFactory.getLogger(ImageProcessingService.class);
    private static final long TARGET_SIZE_BYTES = 2 * 1024 * 1024; // 2MB 目标大小
    private static final int RESOLUTION_THRESHOLD = 1080; // 分辨率阈值 (px)
    private static final float MIN_JPEG_QUALITY = 0.4f; // 可接受的最低 JPEG 质量

    /**
     * 处理单个上传的图片文件。
     *
     * @param inputFile    上传的图片文件。
     * @param outputFormat 期望的输出格式 (当前仅支持 JPEG)。
     * @return 包含压缩后图片数据的字节数组，如果处理失败或无法满足目标大小则返回 null。
     * @throws IOException 如果在文件读取或图片写入过程中发生错误。
     * @throws InterruptedException 如果处理线程被中断 (现在没有外部进程，可能性较小)。
     */
    public byte[] processImage(MultipartFile inputFile, OutputFormat outputFormat) throws IOException, InterruptedException {
        // 尽管传入了格式参数，但目前我们只处理 JPEG
        if (outputFormat != OutputFormat.JPEG) {
             logger.warn("processImage 被调用时使用了不支持的格式: {}. 当前仅支持 JPEG.", outputFormat);
             // 或者抛出 IllegalArgumentException? 目前仅记录警告并假定目标是 JPEG
             // throw new IllegalArgumentException("Unsupported format: " + outputFormat);
        }

        String originalFileName = inputFile.getOriginalFilename() != null ? inputFile.getOriginalFilename() : "unknown_file";
        BufferedImage image = null;

        logger.info("处理图片: {}, 目标格式: JPEG", originalFileName);

        try (InputStream inputStream = inputFile.getInputStream()) {
            logger.debug("读取图片数据: {}", originalFileName);
            image = ImageIO.read(inputStream);
            if (image == null) {
                logger.error("读取图片失败或格式不支持: {}", originalFileName);
                return null;
            }

            logger.debug("必要时调整图片尺寸: {}", originalFileName);
            image = resizeImageIfNecessary(image, originalFileName);

            logger.debug("压缩图片为 JPEG: {}", originalFileName);
            // 直接调用 compressJpeg，因为它是唯一的选项
            byte[] compressedData = compressJpeg(image, new ByteArrayOutputStream(), originalFileName);

            if (compressedData == null) {
                logger.warn("JPEG 压缩失败或未达到目标大小: {}", originalFileName);
                return null;
            }

            if (compressedData.length > TARGET_SIZE_BYTES) {
                logger.error("内部错误: JPEG 压缩后大小 {} > 2MB，文件: {}", compressedData.length, originalFileName);
                return null;
            }

            logger.info("成功处理图片 {} 为 JPEG, 大小: {}",
                    originalFileName, formatFileSize(compressedData.length));
            return compressedData;

        } finally {
            // 帮助垃圾回收
            if (image != null) {
                image.flush();
            }
        }
    }

    private BufferedImage resizeImageIfNecessary(BufferedImage originalImage, String fileName) throws IOException {
        int width = originalImage.getWidth();
        int height = originalImage.getHeight();
        int shorterSide = Math.min(width, height);

        if (shorterSide > RESOLUTION_THRESHOLD) {
            logger.debug("调整尺寸 {}: 最短边 {}px > {}px", fileName, shorterSide, RESOLUTION_THRESHOLD);
            double scaleFactor = (double) RESOLUTION_THRESHOLD / shorterSide;
            int newWidth = (int) Math.round(width * scaleFactor);
            int newHeight = (int) Math.round(height * scaleFactor);

            // 使用 Thumbnailator 进行高质量缩放
            return Thumbnails.of(originalImage)
                    .size(newWidth, newHeight)
                    .keepAspectRatio(true)
                    .asBufferedImage();
        } else {
            logger.debug("无需调整尺寸: {}, 最短边 {}px", fileName, shorterSide);
            return originalImage;
        }
    }

    private byte[] compressJpeg(BufferedImage image, ByteArrayOutputStream baos, String originalFileName) throws IOException {
        float quality = 0.9f; // 从高质量开始
        byte[] data = null;
        ImageWriter jpgWriter = null;
        ImageOutputStream ios = null;

        try {
            // 查找 JPEG 写入器
            ImageWriter foundWriter = null;
            Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpg");
            if (writers.hasNext()) {
                foundWriter = writers.next();
            } else {
                throw new IOException("在 ImageIO 注册表中未找到 JPG 写入器。");
            }
            jpgWriter = foundWriter;

            do {
                baos.reset(); // 重置流以进行下一次尝试
                ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam();
                // 检查是否支持压缩设置
                if (jpgWriteParam.canWriteCompressed()) {
                    jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
                    jpgWriteParam.setCompressionQuality(quality);
                } else if (quality < 0.9f) {
                     // 如果不支持质量调整，且已尝试降低质量，则退出循环
                     logger.warn("JPEG 写入器不支持质量调整: {}", originalFileName);
                    break;
                }

                ios = ImageIO.createImageOutputStream(baos);
                if (ios == null) {
                     throw new IOException("无法创建 ImageOutputStream。");
                }
                jpgWriter.setOutput(ios);
                // 创建 IIOImage 时不传递元数据
                IIOImage outputImage = new IIOImage(image, null, null);
                jpgWriter.write(null, outputImage, jpgWriteParam);
                ios.flush(); // 确保数据写入 baos
                data = baos.toByteArray();
                ios.close(); // 在检查大小前关闭流
                ios = null; // 标记为已关闭

                logger.debug("JPEG 尝试 (Q={}) 文件 {}: 大小 {}", String.format("%.2f", quality), originalFileName, formatFileSize(data.length));

                // 检查是否满足大小要求
                if (data.length <= TARGET_SIZE_BYTES) {
                    logger.info("JPEG 压缩成功 文件 {} 质量 Q={}", originalFileName, String.format("%.2f", quality));
                    return data;
                }

                // 降低质量进行下一次迭代
                if (quality > 0.6f) quality -= 0.1f; // 较高质量时步长大
                else quality -= 0.05f; // 较低质量时步长小

            } while (quality >= MIN_JPEG_QUALITY); // 循环直到达到最低质量阈值

            // 如果循环结束仍未满足要求
            logger.warn("无法将 JPEG {} 压缩到 2MB 以下，即使在最低质量 Q={}", originalFileName, String.format("%.2f", MIN_JPEG_QUALITY));
            return null; // 返回 null 表示失败
        } finally {
             // 确保资源被释放
             if (ios != null) try { ios.close(); } catch (IOException e) { logger.warn("关闭 ImageOutputStream 时出错", e); }
            if (jpgWriter != null) jpgWriter.dispose();
        }
    }

    /**
     * 为 JPEG 输出生成建议的输出文件名。
     *
     * @param originalFileName 上传时的原始文件名。
     * @param format         目标输出格式 (现在应始终为 JPEG)。
     * @return 压缩后文件的建议文件名字符串 (例如: image_compressed.jpg)。
     */
    public String generateOutputFilename(String originalFileName, OutputFormat format) {
        String nameWithoutExtension = originalFileName;
        int dotIndex = originalFileName.lastIndexOf('.');
        if (dotIndex > 0) {
            nameWithoutExtension = originalFileName.substring(0, dotIndex);
        }
        // 由于移除非JPG格式，隐式地移除了对中文字符的需求，重新考虑是否需要净化文件名
        // 如果原始JPG文件名可能包含其他非ASCII字符，则可能需要净化
        // nameWithoutExtension = nameWithoutExtension.replaceAll("[^a-zA-Z0-9_.-]", "_");

        // 现在总是使用 JPEG 扩展名
        return nameWithoutExtension + "_compressed" + OutputFormat.JPEG.getExtension();
    }

    /**
     * 格式化文件大小为易读字符串 (B, KiB, MiB 等)。
     * @param bytes 文件大小 (字节)
     * @return 格式化后的字符串
     */
    public static String formatFileSize(long bytes) {
        long absB = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes);
        if (absB < 1024) {
            return bytes + " B";
        }
        long value = absB;
        // 使用 CharacterIterator 迭代单位 (KiB, MiB, GiB...)
        java.text.CharacterIterator ci = new java.text.StringCharacterIterator("KMGTPE");
        // 这个循环基于一个稍微复杂的方法来确定合适的单位，效果与不断除以1024类似
        for (int i = 40; i >= 0 && absB > 0xfffccccccccccccL >> i; i -= 10) {
            value >>= 10; // 相当于 value /= 1024
            ci.next(); // 移动到下一个单位 (K -> M -> G ...)
        }
        value *= Long.signum(bytes); // 恢复符号位
        // 格式化为一位小数，使用正确的单位，注意 %ciB 会自动添加 'i' 表示二进制单位
        return String.format("%.1f %ciB", value / 1024.0, ci.current());
    }
} 